home *** CD-ROM | disk | FTP | other *** search
/ American Osteopathic Ass…tion Yearbook 2005 & 2006 / American Osteopathic Association Yearbook 2005 & 2006.iso / mac / app / urllib2.pyo (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2004-03-22  |  53.4 KB  |  1,222 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyo (Python 2.3)
  3.  
  4. '''An extensible library for opening URLs using a variety of protocols
  5.  
  6. The simplest way to use this module is to call the urlopen function,
  7. which accepts a string containing a URL or a Request object (described
  8. below).  It opens the URL and returns the results as file-like
  9. object; the returned object has some extra methods described below.
  10.  
  11. The OpenerDirector manages a collection of Handler objects that do
  12. all the actual work.  Each Handler implements a particular protocol or
  13. option.  The OpenerDirector is a composite object that invokes the
  14. Handlers needed to open the requested URL.  For example, the
  15. HTTPHandler performs HTTP GET and POST requests and deals with
  16. non-error returns.  The HTTPRedirectHandler automatically deals with
  17. HTTP 301, 302, 303 and 307 redirect errors, and the HTTPDigestAuthHandler
  18. deals with digest authentication.
  19.  
  20. urlopen(url, data=None) -- basic usage is that same as original
  21. urllib.  pass the url and optionally data to post to an HTTP URL, and
  22. get a file-like object back.  One difference is that you can also pass
  23. a Request instance instead of URL.  Raises a URLError (subclass of
  24. IOError); for HTTP errors, raises an HTTPError, which can also be
  25. treated as a valid response.
  26.  
  27. build_opener -- function that creates a new OpenerDirector instance.
  28. will install the default handlers.  accepts one or more Handlers as
  29. arguments, either instances or Handler classes that it will
  30. instantiate.  if one of the argument is a subclass of the default
  31. handler, the argument will be installed instead of the default.
  32.  
  33. install_opener -- installs a new opener as the default opener.
  34.  
  35. objects of interest:
  36. OpenerDirector --
  37.  
  38. Request -- an object that encapsulates the state of a request.  the
  39. state can be a simple as the URL.  it can also include extra HTTP
  40. headers, e.g. a User-Agent.
  41.  
  42. BaseHandler --
  43.  
  44. exceptions:
  45. URLError-- a subclass of IOError, individual protocols have their own
  46. specific subclass
  47.  
  48. HTTPError-- also a valid HTTP response, so you can treat an HTTP error
  49. as an exceptional event or valid response
  50.  
  51. internals:
  52. BaseHandler and parent
  53. _call_chain conventions
  54.  
  55. Example usage:
  56.  
  57. import urllib2
  58.  
  59. # set up authentication info
  60. authinfo = urllib2.HTTPBasicAuthHandler()
  61. authinfo.add_password(\'realm\', \'host\', \'username\', \'password\')
  62.  
  63. proxy_support = urllib2.ProxyHandler({"http" : "http://ahad-haam:3128"})
  64.  
  65. # build a new opener that adds authentication and caching FTP handlers
  66. opener = urllib2.build_opener(proxy_support, authinfo, urllib2.CacheFTPHandler)
  67.  
  68. # install it
  69. urllib2.install_opener(opener)
  70.  
  71. f = urllib2.urlopen(\'http://www.python.org/\')
  72.  
  73.  
  74. '''
  75. import socket
  76. import httplib
  77. import inspect
  78. import re
  79. import base64
  80. import urlparse
  81. import md5
  82. import mimetypes
  83. import mimetools
  84. import rfc822
  85. import ftplib
  86. import sys
  87. import time
  88. import os
  89. import gopherlib
  90. import posixpath
  91.  
  92. try:
  93.     from cStringIO import StringIO
  94. except ImportError:
  95.     from StringIO import StringIO
  96.  
  97.  
  98. try:
  99.     import sha
  100. except ImportError:
  101.     sha = None
  102.  
  103. from urllib import unwrap, unquote, splittype, splithost, addinfourl, splitport, splitgophertype, splitquery, splitattr, ftpwrapper, noheaders
  104. from urllib import getproxies
  105. from urllib import localhost, url2pathname
  106. __version__ = '2.0a1'
  107. _opener = None
  108.  
  109. def urlopen(url, data = None):
  110.     global _opener
  111.     if _opener is None:
  112.         _opener = build_opener()
  113.     
  114.     return _opener.open(url, data)
  115.  
  116.  
  117. def install_opener(opener):
  118.     global _opener
  119.     _opener = opener
  120.  
  121.  
  122. class URLError(IOError):
  123.     
  124.     def __init__(self, reason):
  125.         self.reason = reason
  126.  
  127.     
  128.     def __str__(self):
  129.         return '<urlopen error %s>' % self.reason
  130.  
  131.  
  132.  
  133. class HTTPError(URLError, addinfourl):
  134.     '''Raised when HTTP error occurs, but also acts like non-error return'''
  135.     __super_init = addinfourl.__init__
  136.     
  137.     def __init__(self, url, code, msg, hdrs, fp):
  138.         self.code = code
  139.         self.msg = msg
  140.         self.hdrs = hdrs
  141.         self.fp = fp
  142.         self.filename = url
  143.         if fp is not None:
  144.             self._HTTPError__super_init(fp, hdrs, url)
  145.         
  146.  
  147.     
  148.     def __str__(self):
  149.         return 'HTTP Error %s: %s' % (self.code, self.msg)
  150.  
  151.     
  152.     def __del__(self):
  153.         if self.fp:
  154.             self.fp.close()
  155.         
  156.  
  157.  
  158.  
  159. class GopherError(URLError):
  160.     pass
  161.  
  162.  
  163. class Request:
  164.     
  165.     def __init__(self, url, data = None, headers = { }):
  166.         self._Request__original = unwrap(url)
  167.         self.type = None
  168.         self.host = None
  169.         self.port = None
  170.         self.data = data
  171.         self.headers = { }
  172.         for key, value in headers.items():
  173.             self.add_header(key, value)
  174.         
  175.  
  176.     
  177.     def __getattr__(self, attr):
  178.         if attr[:12] == '_Request__r_':
  179.             name = attr[12:]
  180.             if hasattr(Request, 'get_' + name):
  181.                 getattr(self, 'get_' + name)()
  182.                 return getattr(self, attr)
  183.             
  184.         
  185.         raise AttributeError, attr
  186.  
  187.     
  188.     def get_method(self):
  189.         if self.has_data():
  190.             return 'POST'
  191.         else:
  192.             return 'GET'
  193.  
  194.     
  195.     def add_data(self, data):
  196.         self.data = data
  197.  
  198.     
  199.     def has_data(self):
  200.         return self.data is not None
  201.  
  202.     
  203.     def get_data(self):
  204.         return self.data
  205.  
  206.     
  207.     def get_full_url(self):
  208.         return self._Request__original
  209.  
  210.     
  211.     def get_type(self):
  212.         if self.type is None:
  213.             (self.type, self._Request__r_type) = splittype(self._Request__original)
  214.             if self.type is None:
  215.                 raise ValueError, 'unknown url type: %s' % self._Request__original
  216.             
  217.         
  218.         return self.type
  219.  
  220.     
  221.     def get_host(self):
  222.         if self.host is None:
  223.             (self.host, self._Request__r_host) = splithost(self._Request__r_type)
  224.             if self.host:
  225.                 self.host = unquote(self.host)
  226.             
  227.         
  228.         return self.host
  229.  
  230.     
  231.     def get_selector(self):
  232.         return self._Request__r_host
  233.  
  234.     
  235.     def set_proxy(self, host, type):
  236.         (self.host, self.type) = (host, type)
  237.         self._Request__r_host = self._Request__original
  238.  
  239.     
  240.     def add_header(self, key, val):
  241.         self.headers[key.capitalize()] = val
  242.  
  243.  
  244.  
  245. class OpenerDirector:
  246.     
  247.     def __init__(self):
  248.         server_version = 'Python-urllib/%s' % __version__
  249.         self.addheaders = [
  250.             ('User-agent', server_version)]
  251.         self.handlers = []
  252.         self.handle_open = { }
  253.         self.handle_error = { }
  254.  
  255.     
  256.     def add_handler(self, handler):
  257.         added = 0
  258.         for meth in dir(handler):
  259.             if meth[-5:] == '_open':
  260.                 protocol = meth[:-5]
  261.                 if protocol in self.handle_open:
  262.                     self.handle_open[protocol].append(handler)
  263.                     self.handle_open[protocol].sort()
  264.                 else:
  265.                     self.handle_open[protocol] = [
  266.                         handler]
  267.                 added = 1
  268.                 continue
  269.             
  270.             i = meth.find('_')
  271.             j = meth[i + 1:].find('_') + i + 1
  272.             if j != -1 and meth[i + 1:j] == 'error':
  273.                 proto = meth[:i]
  274.                 kind = meth[j + 1:]
  275.                 
  276.                 try:
  277.                     kind = int(kind)
  278.                 except ValueError:
  279.                     pass
  280.  
  281.                 dict = self.handle_error.get(proto, { })
  282.                 if kind in dict:
  283.                     dict[kind].append(handler)
  284.                     dict[kind].sort()
  285.                 else:
  286.                     dict[kind] = [
  287.                         handler]
  288.                 self.handle_error[proto] = dict
  289.                 added = 1
  290.                 continue
  291.                 continue
  292.         
  293.         if added:
  294.             self.handlers.append(handler)
  295.             self.handlers.sort()
  296.             handler.add_parent(self)
  297.         
  298.  
  299.     
  300.     def __del__(self):
  301.         self.close()
  302.  
  303.     
  304.     def close(self):
  305.         for handler in self.handlers:
  306.             handler.close()
  307.         
  308.         self.handlers = []
  309.  
  310.     
  311.     def _call_chain(self, chain, kind, meth_name, *args):
  312.         handlers = chain.get(kind, ())
  313.         for handler in handlers:
  314.             func = getattr(handler, meth_name)
  315.             result = func(*args)
  316.             if result is not None:
  317.                 return result
  318.                 continue
  319.         
  320.  
  321.     
  322.     def open(self, fullurl, data = None):
  323.         if isinstance(fullurl, basestring):
  324.             req = Request(fullurl, data)
  325.         else:
  326.             req = fullurl
  327.             if data is not None:
  328.                 req.add_data(data)
  329.             
  330.         result = self._call_chain(self.handle_open, 'default', 'default_open', req)
  331.         if result:
  332.             return result
  333.         
  334.         type_ = req.get_type()
  335.         result = self._call_chain(self.handle_open, type_, type_ + '_open', req)
  336.         if result:
  337.             return result
  338.         
  339.         return self._call_chain(self.handle_open, 'unknown', 'unknown_open', req)
  340.  
  341.     
  342.     def error(self, proto, *args):
  343.         if proto in [
  344.             'http',
  345.             'https']:
  346.             dict = self.handle_error['http']
  347.             proto = args[2]
  348.             meth_name = 'http_error_%d' % proto
  349.             http_err = 1
  350.             orig_args = args
  351.         else:
  352.             dict = self.handle_error
  353.             meth_name = proto + '_error'
  354.             http_err = 0
  355.         args = (dict, proto, meth_name) + args
  356.         result = self._call_chain(*args)
  357.         if result:
  358.             return result
  359.         
  360.         if http_err:
  361.             args = (dict, 'default', 'http_error_default') + orig_args
  362.             return self._call_chain(*args)
  363.         
  364.  
  365.  
  366.  
  367. def build_opener(*handlers):
  368.     '''Create an opener object from a list of handlers.
  369.  
  370.     The opener will use several default handlers, including support
  371.     for HTTP and FTP.
  372.  
  373.     If any of the handlers passed as arguments are subclasses of the
  374.     default handlers, the default handlers will not be used.
  375.     '''
  376.     opener = OpenerDirector()
  377.     default_classes = [
  378.         ProxyHandler,
  379.         UnknownHandler,
  380.         HTTPHandler,
  381.         HTTPDefaultErrorHandler,
  382.         HTTPRedirectHandler,
  383.         FTPHandler,
  384.         FileHandler]
  385.     if hasattr(httplib, 'HTTPS'):
  386.         default_classes.append(HTTPSHandler)
  387.     
  388.     skip = []
  389.     for klass in default_classes:
  390.         for check in handlers:
  391.             if inspect.isclass(check):
  392.                 if issubclass(check, klass):
  393.                     skip.append(klass)
  394.                 
  395.             issubclass(check, klass)
  396.             if isinstance(check, klass):
  397.                 skip.append(klass)
  398.                 continue
  399.         
  400.     
  401.     for klass in skip:
  402.         default_classes.remove(klass)
  403.     
  404.     for klass in default_classes:
  405.         opener.add_handler(klass())
  406.     
  407.     for h in handlers:
  408.         if inspect.isclass(h):
  409.             h = h()
  410.         
  411.         opener.add_handler(h)
  412.     
  413.     return opener
  414.  
  415.  
  416. class BaseHandler:
  417.     handler_order = 500
  418.     
  419.     def add_parent(self, parent):
  420.         self.parent = parent
  421.  
  422.     
  423.     def close(self):
  424.         self.parent = None
  425.  
  426.     
  427.     def __lt__(self, other):
  428.         if not hasattr(other, 'handler_order'):
  429.             return True
  430.         
  431.         return self.handler_order < other.handler_order
  432.  
  433.  
  434.  
  435. class HTTPDefaultErrorHandler(BaseHandler):
  436.     
  437.     def http_error_default(self, req, fp, code, msg, hdrs):
  438.         raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
  439.  
  440.  
  441.  
  442. class HTTPRedirectHandler(BaseHandler):
  443.     
  444.     def redirect_request(self, req, fp, code, msg, headers, newurl):
  445.         """Return a Request or None in response to a redirect.
  446.  
  447.         This is called by the http_error_30x methods when a
  448.         redirection response is received.  If a redirection should
  449.         take place, return a new Request to allow http_error_30x to
  450.         perform the redirect.  Otherwise, raise HTTPError if no-one
  451.         else should try to handle this url.  Return None if you can't
  452.         but another Handler might.
  453.         """
  454.         m = req.get_method()
  455.         if code in (301, 302, 303, 307) and m in ('GET', 'HEAD') and code in (301, 302, 303) and m == 'POST':
  456.             return Request(newurl, headers = req.headers)
  457.         else:
  458.             raise HTTPError(req.get_full_url(), code, msg, headers, fp)
  459.  
  460.     
  461.     def http_error_302(self, req, fp, code, msg, headers):
  462.         if 'location' in headers:
  463.             newurl = headers['location']
  464.         elif 'uri' in headers:
  465.             newurl = headers['uri']
  466.         else:
  467.             return None
  468.         newurl = urlparse.urljoin(req.get_full_url(), newurl)
  469.         new = self.redirect_request(req, fp, code, msg, headers, newurl)
  470.         if new is None:
  471.             return None
  472.         
  473.         new.error_302_dict = { }
  474.         if hasattr(req, 'error_302_dict'):
  475.             if len(req.error_302_dict) > 10 or newurl in req.error_302_dict:
  476.                 raise HTTPError(req.get_full_url(), code, self.inf_msg + msg, headers, fp)
  477.             
  478.             new.error_302_dict.update(req.error_302_dict)
  479.         
  480.         new.error_302_dict[newurl] = newurl
  481.         fp.read()
  482.         fp.close()
  483.         return self.parent.open(new)
  484.  
  485.     http_error_301 = http_error_303 = http_error_307 = http_error_302
  486.     inf_msg = 'The HTTP server returned a redirect error that would lead to an infinite loop.\nThe last 30x error message was:\n'
  487.  
  488.  
  489. class ProxyHandler(BaseHandler):
  490.     handler_order = 100
  491.     
  492.     def __init__(self, proxies = None):
  493.         if proxies is None:
  494.             proxies = getproxies()
  495.         
  496.         self.proxies = proxies
  497.         for type, url in proxies.items():
  498.             setattr(self, '%s_open' % type, (lambda r, proxy = url, type = type, meth = self.proxy_open: meth(r, proxy, type)))
  499.         
  500.  
  501.     
  502.     def proxy_open(self, req, proxy, type):
  503.         orig_type = req.get_type()
  504.         (type, r_type) = splittype(proxy)
  505.         (host, XXX) = splithost(r_type)
  506.         if '@' in host:
  507.             (user_pass, host) = host.split('@', 1)
  508.             if ':' in user_pass:
  509.                 (user, password) = user_pass.split(':', 1)
  510.                 user_pass = base64.encodestring('%s:%s' % (unquote(user), unquote(password)))
  511.                 req.add_header('Proxy-authorization', 'Basic ' + user_pass)
  512.             
  513.         
  514.         host = unquote(host)
  515.         req.set_proxy(host, type)
  516.         if orig_type == type:
  517.             return None
  518.         else:
  519.             return self.parent.open(req)
  520.  
  521.  
  522.  
  523. class CustomProxy:
  524.     
  525.     def __init__(self, proto, func = None, proxy_addr = None):
  526.         self.proto = proto
  527.         self.func = func
  528.         self.addr = proxy_addr
  529.  
  530.     
  531.     def handle(self, req):
  532.         if self.func and self.func(req):
  533.             return 1
  534.         
  535.  
  536.     
  537.     def get_proxy(self):
  538.         return self.addr
  539.  
  540.  
  541.  
  542. class CustomProxyHandler(BaseHandler):
  543.     handler_order = 100
  544.     
  545.     def __init__(self, *proxies):
  546.         self.proxies = { }
  547.  
  548.     
  549.     def proxy_open(self, req):
  550.         proto = req.get_type()
  551.         
  552.         try:
  553.             proxies = self.proxies[proto]
  554.         except KeyError:
  555.             return None
  556.  
  557.         for p in proxies:
  558.             if p.handle(req):
  559.                 req.set_proxy(p.get_proxy())
  560.                 return self.parent.open(req)
  561.                 continue
  562.         
  563.         return None
  564.  
  565.     
  566.     def do_proxy(self, p, req):
  567.         return self.parent.open(req)
  568.  
  569.     
  570.     def add_proxy(self, cpo):
  571.         if cpo.proto in self.proxies:
  572.             self.proxies[cpo.proto].append(cpo)
  573.         else:
  574.             self.proxies[cpo.proto] = [
  575.                 cpo]
  576.  
  577.  
  578.  
  579. class HTTPPasswordMgr:
  580.     
  581.     def __init__(self):
  582.         self.passwd = { }
  583.  
  584.     
  585.     def add_password(self, realm, uri, user, passwd):
  586.         if isinstance(uri, basestring):
  587.             uri = [
  588.                 uri]
  589.         
  590.         uri = tuple(map(self.reduce_uri, uri))
  591.         if not (realm in self.passwd):
  592.             self.passwd[realm] = { }
  593.         
  594.         self.passwd[realm][uri] = (user, passwd)
  595.  
  596.     
  597.     def find_user_password(self, realm, authuri):
  598.         domains = self.passwd.get(realm, { })
  599.         authuri = self.reduce_uri(authuri)
  600.         for uris, authinfo in domains.iteritems():
  601.             for uri in uris:
  602.                 if self.is_suburi(uri, authuri):
  603.                     return authinfo
  604.                     continue
  605.             
  606.         
  607.         return (None, None)
  608.  
  609.     
  610.     def reduce_uri(self, uri):
  611.         '''Accept netloc or URI and extract only the netloc and path'''
  612.         parts = urlparse.urlparse(uri)
  613.         if parts[1]:
  614.             if not parts[2]:
  615.                 pass
  616.             return (parts[1], '/')
  617.         else:
  618.             return (parts[2], '/')
  619.  
  620.     
  621.     def is_suburi(self, base, test):
  622.         '''Check if test is below base in a URI tree
  623.  
  624.         Both args must be URIs in reduced form.
  625.         '''
  626.         if base == test:
  627.             return True
  628.         
  629.         if base[0] != test[0]:
  630.             return False
  631.         
  632.         common = posixpath.commonprefix((base[1], test[1]))
  633.         if len(common) == len(base[1]):
  634.             return True
  635.         
  636.         return False
  637.  
  638.  
  639.  
  640. class HTTPPasswordMgrWithDefaultRealm(HTTPPasswordMgr):
  641.     
  642.     def find_user_password(self, realm, authuri):
  643.         (user, password) = HTTPPasswordMgr.find_user_password(self, realm, authuri)
  644.         if user is not None:
  645.             return (user, password)
  646.         
  647.         return HTTPPasswordMgr.find_user_password(self, None, authuri)
  648.  
  649.  
  650.  
  651. class AbstractBasicAuthHandler:
  652.     rx = re.compile('[ \t]*([^ \t]+)[ \t]+realm="([^"]*)"', re.I)
  653.     
  654.     def __init__(self, password_mgr = None):
  655.         if password_mgr is None:
  656.             password_mgr = HTTPPasswordMgr()
  657.         
  658.         self.passwd = password_mgr
  659.         self.add_password = self.passwd.add_password
  660.  
  661.     
  662.     def http_error_auth_reqed(self, authreq, host, req, headers):
  663.         authreq = headers.get(authreq, None)
  664.         if authreq:
  665.             mo = AbstractBasicAuthHandler.rx.match(authreq)
  666.             if mo:
  667.                 (scheme, realm) = mo.groups()
  668.                 if scheme.lower() == 'basic':
  669.                     return self.retry_http_basic_auth(host, req, realm)
  670.                 
  671.             
  672.         
  673.  
  674.     
  675.     def retry_http_basic_auth(self, host, req, realm):
  676.         (user, pw) = self.passwd.find_user_password(realm, host)
  677.         if pw:
  678.             raw = '%s:%s' % (user, pw)
  679.             auth = 'Basic %s' % base64.encodestring(raw).strip()
  680.             if req.headers.get(self.auth_header, None) == auth:
  681.                 return None
  682.             
  683.             req.add_header(self.auth_header, auth)
  684.             return self.parent.open(req)
  685.         else:
  686.             return None
  687.  
  688.  
  689.  
  690. class HTTPBasicAuthHandler(AbstractBasicAuthHandler, BaseHandler):
  691.     auth_header = 'Authorization'
  692.     
  693.     def http_error_401(self, req, fp, code, msg, headers):
  694.         host = urlparse.urlparse(req.get_full_url())[1]
  695.         return self.http_error_auth_reqed('www-authenticate', host, req, headers)
  696.  
  697.  
  698.  
  699. class ProxyBasicAuthHandler(AbstractBasicAuthHandler, BaseHandler):
  700.     auth_header = 'Proxy-authorization'
  701.     
  702.     def http_error_407(self, req, fp, code, msg, headers):
  703.         host = req.get_host()
  704.         return self.http_error_auth_reqed('proxy-authenticate', host, req, headers)
  705.  
  706.  
  707.  
  708. class AbstractDigestAuthHandler:
  709.     
  710.     def __init__(self, passwd = None):
  711.         if passwd is None:
  712.             passwd = HTTPPasswordMgr()
  713.         
  714.         self.passwd = passwd
  715.         self.add_password = self.passwd.add_password
  716.  
  717.     
  718.     def http_error_auth_reqed(self, authreq, host, req, headers):
  719.         authreq = headers.get(self.auth_header, None)
  720.         if authreq:
  721.             kind = authreq.split()[0]
  722.             if kind == 'Digest':
  723.                 return self.retry_http_digest_auth(req, authreq)
  724.             
  725.         
  726.  
  727.     
  728.     def retry_http_digest_auth(self, req, auth):
  729.         (token, challenge) = auth.split(' ', 1)
  730.         chal = parse_keqv_list(parse_http_list(challenge))
  731.         auth = self.get_authorization(req, chal)
  732.         if auth:
  733.             auth_val = 'Digest %s' % auth
  734.             if req.headers.get(self.auth_header, None) == auth_val:
  735.                 return None
  736.             
  737.             req.add_header(self.auth_header, auth_val)
  738.             resp = self.parent.open(req)
  739.             return resp
  740.         
  741.  
  742.     
  743.     def get_authorization(self, req, chal):
  744.         
  745.         try:
  746.             realm = chal['realm']
  747.             nonce = chal['nonce']
  748.             algorithm = chal.get('algorithm', 'MD5')
  749.             opaque = chal.get('opaque', None)
  750.         except KeyError:
  751.             return None
  752.  
  753.         (H, KD) = self.get_algorithm_impls(algorithm)
  754.         if H is None:
  755.             return None
  756.         
  757.         (user, pw) = self.passwd.find_user_password(realm, req.get_full_url())
  758.         if user is None:
  759.             return None
  760.         
  761.         if req.has_data():
  762.             entdig = self.get_entity_digest(req.get_data(), chal)
  763.         else:
  764.             entdig = None
  765.         A1 = '%s:%s:%s' % (user, realm, pw)
  766.         if not req.has_data() and 'POST':
  767.             pass
  768.         A2 = '%s:%s' % ('GET', req.get_selector())
  769.         respdig = KD(H(A1), '%s:%s' % (nonce, H(A2)))
  770.         base = 'username="%s", realm="%s", nonce="%s", uri="%s", response="%s"' % (user, realm, nonce, req.get_selector(), respdig)
  771.         if opaque:
  772.             base = base + ', opaque="%s"' % opaque
  773.         
  774.         if entdig:
  775.             base = base + ', digest="%s"' % entdig
  776.         
  777.         if algorithm != 'MD5':
  778.             base = base + ', algorithm="%s"' % algorithm
  779.         
  780.         return base
  781.  
  782.     
  783.     def get_algorithm_impls(self, algorithm):
  784.         if algorithm == 'MD5':
  785.             
  786.             H = lambda x, e = encode_digest: e(md5.new(x).digest())
  787.         elif algorithm == 'SHA':
  788.             
  789.             H = lambda x, e = encode_digest: e(sha.new(x).digest())
  790.         
  791.         
  792.         KD = lambda s, d, H = H: H('%s:%s' % (s, d))
  793.         return (H, KD)
  794.  
  795.     
  796.     def get_entity_digest(self, data, chal):
  797.         return None
  798.  
  799.  
  800.  
  801. class HTTPDigestAuthHandler(BaseHandler, AbstractDigestAuthHandler):
  802.     '''An authentication protocol defined by RFC 2069
  803.  
  804.     Digest authentication improves on basic authentication because it
  805.     does not transmit passwords in the clear.
  806.     '''
  807.     auth_header = 'Authorization'
  808.     
  809.     def http_error_401(self, req, fp, code, msg, headers):
  810.         host = urlparse.urlparse(req.get_full_url())[1]
  811.         self.http_error_auth_reqed('www-authenticate', host, req, headers)
  812.  
  813.  
  814.  
  815. class ProxyDigestAuthHandler(BaseHandler, AbstractDigestAuthHandler):
  816.     auth_header = 'Proxy-Authorization'
  817.     
  818.     def http_error_407(self, req, fp, code, msg, headers):
  819.         host = req.get_host()
  820.         self.http_error_auth_reqed('proxy-authenticate', host, req, headers)
  821.  
  822.  
  823.  
  824. def encode_digest(digest):
  825.     hexrep = []
  826.     for c in digest:
  827.         n = ord(c) >> 4 & 15
  828.         hexrep.append(hex(n)[-1])
  829.         n = ord(c) & 15
  830.         hexrep.append(hex(n)[-1])
  831.     
  832.     return ''.join(hexrep)
  833.  
  834.  
  835. class AbstractHTTPHandler(BaseHandler):
  836.     
  837.     def do_open(self, http_class, req):
  838.         host = req.get_host()
  839.         if not host:
  840.             raise URLError('no host given')
  841.         
  842.         h = http_class(host)
  843.         if req.has_data():
  844.             data = req.get_data()
  845.             h.putrequest('POST', req.get_selector())
  846.             if not ('Content-type' in req.headers):
  847.                 h.putheader('Content-type', 'application/x-www-form-urlencoded')
  848.             
  849.             if not ('Content-length' in req.headers):
  850.                 h.putheader('Content-length', '%d' % len(data))
  851.             
  852.         else:
  853.             h.putrequest('GET', req.get_selector())
  854.         (scheme, sel) = splittype(req.get_selector())
  855.         (sel_host, sel_path) = splithost(sel)
  856.         if not sel_host:
  857.             pass
  858.         h.putheader('Host', host)
  859.         for name, value in self.parent.addheaders:
  860.             name = name.capitalize()
  861.             if name not in req.headers:
  862.                 h.putheader(name, value)
  863.                 continue
  864.         
  865.         for k, v in req.headers.items():
  866.             h.putheader(k, v)
  867.         
  868.         
  869.         try:
  870.             h.endheaders()
  871.         except socket.error:
  872.             err = None
  873.             raise URLError(err)
  874.  
  875.         if req.has_data():
  876.             h.send(data)
  877.         
  878.         (code, msg, hdrs) = h.getreply()
  879.         fp = h.getfile()
  880.         if code == 200:
  881.             return addinfourl(fp, hdrs, req.get_full_url())
  882.         else:
  883.             return self.parent.error('http', req, fp, code, msg, hdrs)
  884.  
  885.  
  886.  
  887. class HTTPHandler(AbstractHTTPHandler):
  888.     
  889.     def http_open(self, req):
  890.         return self.do_open(httplib.HTTP, req)
  891.  
  892.  
  893. if hasattr(httplib, 'HTTPS'):
  894.     
  895.     class HTTPSHandler(AbstractHTTPHandler):
  896.         
  897.         def https_open(self, req):
  898.             return self.do_open(httplib.HTTPS, req)
  899.  
  900.  
  901.  
  902.  
  903. class UnknownHandler(BaseHandler):
  904.     
  905.     def unknown_open(self, req):
  906.         type = req.get_type()
  907.         raise URLError('unknown url type: %s' % type)
  908.  
  909.  
  910.  
  911. def parse_keqv_list(l):
  912.     '''Parse list of key=value strings where keys are not duplicated.'''
  913.     parsed = { }
  914.     for elt in l:
  915.         (k, v) = elt.split('=', 1)
  916.         if v[0] == '"' and v[-1] == '"':
  917.             v = v[1:-1]
  918.         
  919.         parsed[k] = v
  920.     
  921.     return parsed
  922.  
  923.  
  924. def parse_http_list(s):
  925.     '''Parse lists as described by RFC 2068 Section 2.
  926.  
  927.     In particular, parse comman-separated lists where the elements of
  928.     the list may include quoted-strings.  A quoted-string could
  929.     contain a comma.
  930.     '''
  931.     list = []
  932.     end = len(s)
  933.     i = 0
  934.     inquote = 0
  935.     start = 0
  936.     while i < end:
  937.         cur = s[i:]
  938.         c = cur.find(',')
  939.         q = cur.find('"')
  940.         if c == -1:
  941.             list.append(s[start:])
  942.             break
  943.         
  944.         if q == -1:
  945.             if inquote:
  946.                 raise ValueError, 'unbalanced quotes'
  947.             else:
  948.                 list.append(s[start:i + c])
  949.                 i = i + c + 1
  950.         
  951.         if inquote:
  952.             if q < c:
  953.                 list.append(s[start:i + c])
  954.                 i = i + c + 1
  955.                 start = i
  956.                 inquote = 0
  957.             else:
  958.                 i = i + q
  959.         q < c
  960.         if c < q:
  961.             list.append(s[start:i + c])
  962.             i = i + c + 1
  963.             start = i
  964.             continue
  965.         inquote = 1
  966.         i = i + q + 1
  967.     return map((lambda x: x.strip()), list)
  968.  
  969.  
  970. class FileHandler(BaseHandler):
  971.     
  972.     def file_open(self, req):
  973.         url = req.get_selector()
  974.         if url[:2] == '//' and url[2:3] != '/':
  975.             req.type = 'ftp'
  976.             return self.parent.open(req)
  977.         else:
  978.             return self.open_local_file(req)
  979.  
  980.     names = None
  981.     
  982.     def get_names(self):
  983.         if FileHandler.names is None:
  984.             FileHandler.names = (socket.gethostbyname('localhost'), socket.gethostbyname(socket.gethostname()))
  985.         
  986.         return FileHandler.names
  987.  
  988.     
  989.     def open_local_file(self, req):
  990.         host = req.get_host()
  991.         file = req.get_selector()
  992.         localfile = url2pathname(file)
  993.         stats = os.stat(localfile)
  994.         size = stats.st_size
  995.         modified = rfc822.formatdate(stats.st_mtime)
  996.         mtype = mimetypes.guess_type(file)[0]
  997.         if not mtype:
  998.             pass
  999.         headers = mimetools.Message(StringIO('Content-type: %s\nContent-length: %d\nLast-modified: %s\n' % ('text/plain', size, modified)))
  1000.         if host:
  1001.             (host, port) = splitport(host)
  1002.         
  1003.         if not host and not port and socket.gethostbyname(host) in self.get_names():
  1004.             return addinfourl(open(localfile, 'rb'), headers, 'file:' + file)
  1005.         
  1006.         raise URLError('file not on local host')
  1007.  
  1008.  
  1009.  
  1010. class FTPHandler(BaseHandler):
  1011.     
  1012.     def ftp_open(self, req):
  1013.         host = req.get_host()
  1014.         if not host:
  1015.             raise IOError, ('ftp error', 'no host given')
  1016.         
  1017.         
  1018.         try:
  1019.             host = socket.gethostbyname(host)
  1020.         except socket.error:
  1021.             msg = None
  1022.             raise URLError(msg)
  1023.  
  1024.         (host, port) = splitport(host)
  1025.         if port is None:
  1026.             port = ftplib.FTP_PORT
  1027.         
  1028.         (path, attrs) = splitattr(req.get_selector())
  1029.         path = unquote(path)
  1030.         dirs = path.split('/')
  1031.         (dirs, file) = (dirs[:-1], dirs[-1])
  1032.         if dirs and not dirs[0]:
  1033.             dirs = dirs[1:]
  1034.         
  1035.         user = passwd = ''
  1036.         
  1037.         try:
  1038.             fw = self.connect_ftp(user, passwd, host, port, dirs)
  1039.             if not file and 'I':
  1040.                 pass
  1041.             type = 'D'
  1042.             for attr in attrs:
  1043.                 (attr, value) = splitattr(attr)
  1044.                 if attr.lower() == 'type' and value in ('a', 'A', 'i', 'I', 'd', 'D'):
  1045.                     type = value.upper()
  1046.                     continue
  1047.             
  1048.             (fp, retrlen) = fw.retrfile(file, type)
  1049.             headers = ''
  1050.             mtype = mimetypes.guess_type(req.get_full_url())[0]
  1051.             if mtype:
  1052.                 headers += 'Content-type: %s\n' % mtype
  1053.             
  1054.             if retrlen is not None and retrlen >= 0:
  1055.                 headers += 'Content-length: %d\n' % retrlen
  1056.             
  1057.             sf = StringIO(headers)
  1058.             headers = mimetools.Message(sf)
  1059.             return addinfourl(fp, headers, req.get_full_url())
  1060.         except ftplib.all_errors:
  1061.             msg = None
  1062.             raise IOError, ('ftp error', msg), sys.exc_info()[2]
  1063.  
  1064.  
  1065.     
  1066.     def connect_ftp(self, user, passwd, host, port, dirs):
  1067.         fw = ftpwrapper(user, passwd, host, port, dirs)
  1068.         return fw
  1069.  
  1070.  
  1071.  
  1072. class CacheFTPHandler(FTPHandler):
  1073.     
  1074.     def __init__(self):
  1075.         self.cache = { }
  1076.         self.timeout = { }
  1077.         self.soonest = 0
  1078.         self.delay = 60
  1079.         self.max_conns = 16
  1080.  
  1081.     
  1082.     def setTimeout(self, t):
  1083.         self.delay = t
  1084.  
  1085.     
  1086.     def setMaxConns(self, m):
  1087.         self.max_conns = m
  1088.  
  1089.     
  1090.     def connect_ftp(self, user, passwd, host, port, dirs):
  1091.         key = (user, passwd, host, port)
  1092.         if key in self.cache:
  1093.             self.timeout[key] = time.time() + self.delay
  1094.         else:
  1095.             self.cache[key] = ftpwrapper(user, passwd, host, port, dirs)
  1096.             self.timeout[key] = time.time() + self.delay
  1097.         self.check_cache()
  1098.         return self.cache[key]
  1099.  
  1100.     
  1101.     def check_cache(self):
  1102.         t = time.time()
  1103.         if self.soonest <= t:
  1104.             for k, v in self.timeout.items():
  1105.                 if v < t:
  1106.                     self.cache[k].close()
  1107.                     del self.cache[k]
  1108.                     del self.timeout[k]
  1109.                     continue
  1110.             
  1111.         
  1112.         self.soonest = min(self.timeout.values())
  1113.         if len(self.cache) == self.max_conns:
  1114.             for k, v in self.timeout.items():
  1115.                 if v == self.soonest:
  1116.                     del self.cache[k]
  1117.                     del self.timeout[k]
  1118.                     break
  1119.                     continue
  1120.             
  1121.             self.soonest = min(self.timeout.values())
  1122.         
  1123.  
  1124.  
  1125.  
  1126. class GopherHandler(BaseHandler):
  1127.     
  1128.     def gopher_open(self, req):
  1129.         host = req.get_host()
  1130.         if not host:
  1131.             raise GopherError('no host given')
  1132.         
  1133.         host = unquote(host)
  1134.         selector = req.get_selector()
  1135.         (type, selector) = splitgophertype(selector)
  1136.         (selector, query) = splitquery(selector)
  1137.         selector = unquote(selector)
  1138.         if query:
  1139.             query = unquote(query)
  1140.             fp = gopherlib.send_query(selector, query, host)
  1141.         else:
  1142.             fp = gopherlib.send_selector(selector, host)
  1143.         return addinfourl(fp, noheaders(), req.get_full_url())
  1144.  
  1145.  
  1146.  
  1147. class OpenerFactory:
  1148.     default_handlers = [
  1149.         UnknownHandler,
  1150.         HTTPHandler,
  1151.         HTTPDefaultErrorHandler,
  1152.         HTTPRedirectHandler,
  1153.         FTPHandler,
  1154.         FileHandler]
  1155.     handlers = []
  1156.     replacement_handlers = []
  1157.     
  1158.     def add_handler(self, h):
  1159.         self.handlers = self.handlers + [
  1160.             h]
  1161.  
  1162.     
  1163.     def replace_handler(self, h):
  1164.         pass
  1165.  
  1166.     
  1167.     def build_opener(self):
  1168.         opener = OpenerDirector()
  1169.         for ph in self.default_handlers:
  1170.             if inspect.isclass(ph):
  1171.                 ph = ph()
  1172.             
  1173.             opener.add_handler(ph)
  1174.         
  1175.  
  1176.  
  1177. if __name__ == '__main__':
  1178.     if socket.gethostname() == 'bitdiddle':
  1179.         localhost = 'bitdiddle.cnri.reston.va.us'
  1180.     elif socket.gethostname() == 'bitdiddle.concentric.net':
  1181.         localhost = 'localhost'
  1182.     else:
  1183.         localhost = None
  1184.     urls = [
  1185.         'gopher://gopher.lib.ncsu.edu/11/library/stacks/Alex',
  1186.         'gopher://gopher.vt.edu:10010/10/33',
  1187.         'file:/etc/passwd',
  1188.         'file://nonsensename/etc/passwd',
  1189.         'ftp://www.python.org/pub/python/misc/sousa.au',
  1190.         'ftp://www.python.org/pub/tmp/blat',
  1191.         'http://www.espn.com/',
  1192.         'http://www.python.org/Spanish/Inquistion/',
  1193.         ('http://www.python.org/cgi-bin/faqw.py', 'query=pythonistas&querytype=simple&casefold=yes&req=search'),
  1194.         'http://www.python.org/',
  1195.         'ftp://gatekeeper.research.compaq.com/pub/DEC/SRC/research-reports/00README-Legal-Rules-Regs']
  1196.     cfh = CacheFTPHandler()
  1197.     cfh.setTimeout(1)
  1198.     install_opener(build_opener(cfh, GopherHandler))
  1199.     for url in urls:
  1200.         if isinstance(url, tuple):
  1201.             (url, req) = url
  1202.         else:
  1203.             req = None
  1204.         print url
  1205.         
  1206.         try:
  1207.             f = urlopen(url, req)
  1208.         except IOError:
  1209.             err = None
  1210.             print 'IOError:', err
  1211.         except socket.error:
  1212.             err = None
  1213.             print 'socket.error:', err
  1214.  
  1215.         buf = f.read()
  1216.         f.close()
  1217.         print 'read %d bytes' % len(buf)
  1218.         print 
  1219.         time.sleep(0.10000000000000001)
  1220.     
  1221.  
  1222.